package com.uit.httt04.duongvanlam.facedetection.algorithm;

import java.util.List;

import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.uit.httt04.duongvanlam.facedetection.classes.ObjectAlgorithm;

import static com.googlecode.javacv.cpp.opencv_core.*;

import android.annotation.SuppressLint;

@SuppressLint(
{ "DefaultLocale", "UseSparseArrays" })
public class Lda_2d_R extends Algorithm
{
	public Lda_2d_R()
	{
		super();
	}

	public void Train() throws Exception
	{
		try
		{
			if (this.getAmountOfObjects() > 1 && this.getAmountOfFaces() > 1)
			{
				//
				// <-- Average class
				//
				CvMat[] averageClass;
				int[] amountElementEachClass;

				amountElementEachClass = CountElementEachClass(this.getAmountOfFaces(), this.getListObjects());
				averageClass = calculateAverageClass(amountElementEachClass, this.getAmountOfFaces(), this.getListObjects(), getHeight(), getWidth());
				//
				// Average class -->
				//

				//
				// <-- Average all
				//
				CvMat averageAll = calculateAverageAll(this.getListObjects(), getHeight(), getWidth());
				//
				// Average all -->
				//

				//
				// <-- G Between-CLass
				//
				CvMat GB = calculateGB(this.getListObjects(), averageClass, amountElementEachClass, averageAll, getHeight(), getWidth());
				//
				// G Between-CLass -->
				//

				//
				// <-- G Within-Class
				//
				CvMat GW = calculateGW(averageClass, this.getListObjects(), getHeight(), getWidth());
				//
				// G Within-Class -->
				//

				//
				// <-- 1/GW
				//
				CvMat invertGW = cvCreateMat(getWidth(), getWidth(), CV_32FC1);
				cvInvert(GW, invertGW);
				//
				// 1/GW -->
				//

				//
				// <-- GB/GW <=> GB*pow(GW,-1)
				//
				CvMat gbMultipleGw = cvCreateMat(getWidth(), getWidth(), CV_32FC1);
				cvMatMul(invertGW, GB, gbMultipleGw);
				//
				// GB/GW <=> GB*pow(GW,-1) -->
				//

				//
				// <-- EigenVector And EigenValue
				//
				this.setEigenVal(cvCreateMat(getWidth(), 1, CV_32FC1));
				cvSetZero(this.getEigenVal());
				this.setEigenvect(cvCreateMat(getWidth(), getWidth(), CV_32FC1));
				cvSetZero(this.getEigenvect());
				cvEigenVV(gbMultipleGw, this.getEigenvect(), this.getEigenVal(), 0, -1, -1);
				//
				// EigenVector And EigenValue -->
				//

				//
				// <-- Free
				//
				cvReleaseMat(averageAll);
				for (int i = 0; i < averageClass.length; i++)
					cvReleaseMat(averageClass[i]);
				cvReleaseMat(GB);
				cvReleaseMat(GW);
				cvReleaseMat(invertGW);
				//
				// Free -->
				//
			}
			else
			{
				throw new Exception("Lda cant train because wrong input");
			}
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private CvMat[] calculateAverageClass(int[] AmountElementEachClass, int amountClasses, List<ObjectAlgorithm> ListObject, int Height, int Width) throws Exception
	{
		try
		{
			CvMat[] result;
			//
			// <-- Initialize and set zero for averageClass
			//
			result = new CvMat[amountClasses];
			for (int i = 0; i < amountClasses; i++)
			{
				result[i] = cvCreateMat(Height, Width, CV_32FC1);
				cvSetZero(result[i]);
			}
			//
			// Initialize and set zero for averageClass -->
			//
			// Sum
			for (int i = 0; i < ListObject.size(); i++)
			{
				int personId = ListObject.get(i).getID();
				cvAdd(result[personId], ListObject.get(i).getMatrix(), result[personId], null);
			}

			// Divide
			for (int i = 0; i < amountClasses; i++)
			{
				double coefficient = (double) ((double) 1 / (double) AmountElementEachClass[i]);
				cvConvertScale(result[i], result[i], coefficient, 0);
			}

			return result;
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private CvMat calculateAverageAll(List<ObjectAlgorithm> ListObject, int Height, int Width) throws Exception
	{
		try
		{
			CvMat result = cvCreateMat(Height, Width, CV_32FC1);
			cvSetZero(result);

			for (int i = 0; i < ListObject.size(); i++)
			{
				cvAdd(result, ListObject.get(i).getMatrix(), result, null);
			}

			double coefficient = (double) ((double) 1 / (double) ListObject.size());
			cvConvertScale(result, result, coefficient, 0);

			return result;
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private CvMat calculateGB(List<ObjectAlgorithm> ListObject, CvMat[] AverageClass, int[] AmountElementEachClass, CvMat AverageAll, int Height, int Width) throws Exception
	{
		try
		{
			CvMat Delta = cvCreateMat(Height, Width, CV_32FC1);
			CvMat DeltaT = cvCreateMat(Width, Height, CV_32FC1);
			// tempMatMultiple = DeltaT * Delta
			CvMat tempMatMultiple = cvCreateMat(Width, Width, CV_32FC1);
			// covariantMatrix = covariantMatrix + tempMatMultiple
			CvMat result = cvCreateMat(Width, Width, CV_32FC1);
			cvSetZero(result);

			for (int i = 0; i < AverageClass.length; i++)
			{
				//
				cvSetZero(Delta);
				cvSetZero(DeltaT);
				cvSetZero(tempMatMultiple);
				//
				cvSub(AverageClass[i], AverageAll, Delta, null);
				cvTranspose(Delta, DeltaT);
				cvMatMul(DeltaT, Delta, tempMatMultiple);
				//double coefficient = (double) ((double) AmountElementEachClass[i] / (double) listObject.size());
				cvConvertScale(tempMatMultiple, tempMatMultiple, AmountElementEachClass[i], 0);
				cvAdd(result, tempMatMultiple, result, null);
			}

			//
			// <-- Free
			//
			cvReleaseMat(Delta);
			cvReleaseMat(DeltaT);
			cvReleaseMat(tempMatMultiple);
			//
			// Free -->
			//

			return result;
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private CvMat calculateGW(CvMat[] AverageClass, List<ObjectAlgorithm> ListObject, int Height, int Width) throws Exception
	{
		try
		{
			CvMat Delta = cvCreateMat(Height, Width, CV_32FC1);
			CvMat DeltaT = cvCreateMat(Width, Height, CV_32FC1);
			// tempMatMultiple = DeltaT * Delta
			CvMat tempMatMultiple = cvCreateMat(Width, Width, CV_32FC1);
			// covariantMatrix = covariantMatrix + tempMatMultiple
			CvMat result = cvCreateMat(Width, Width, CV_32FC1);
			cvSetZero(result);
			//

			for (int i = 0; i < ListObject.size(); i++)
			{
				cvSetZero(Delta);
				cvSetZero(DeltaT);
				cvSetZero(tempMatMultiple);
				//
				int personId = ListObject.get(i).getID();
				//
				cvSub(ListObject.get(i).getMatrix(), AverageClass[personId], Delta, null);
				cvTranspose(Delta, DeltaT);
				cvMatMul(DeltaT, Delta, tempMatMultiple);
				cvAdd(result, tempMatMultiple, result, null);
			}

			//
			// <-- Free
			//
			cvReleaseMat(Delta);
			cvReleaseMat(DeltaT);
			cvReleaseMat(tempMatMultiple);
			//
			// Free -->
			//

			return result;
		}
		catch (Exception e)
		{
			throw e;
		}
	}
}
